package com.gun3y.manager;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Map.Entry;

import javax.annotation.PostConstruct;

import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import com.gun3y.entity.media.Data;
import com.gun3y.entity.media.Movie;
import com.gun3y.entity.media.PropertyType;
import com.gun3y.entity.result.ResultField;
import com.gun3y.exception.ExceptionCode;
import com.gun3y.exception.SWException;
import com.gun3y.ts.dao.TripleStore;
import com.gun3y.ts.helper.SparqlHelper;
import com.gun3y.ts.helper.TripleHelper;
import com.gun3y.utils.Commons;
import com.gun3y.utils.ResourceLoader;
import com.hp.hpl.jena.graph.Node;
import com.hp.hpl.jena.graph.NodeFactory;
import com.hp.hpl.jena.graph.Triple;
import com.hp.hpl.jena.query.QuerySolution;
import com.hp.hpl.jena.rdf.model.AnonId;
import com.hp.hpl.jena.rdf.model.RDFNode;
import com.hp.hpl.jena.reasoner.rulesys.GenericRuleReasoner;
import com.hp.hpl.jena.reasoner.rulesys.Rule;

@Service
public class MovieGraphManager {

    @Autowired
    private ResourceLoader resourceLoader;

    private String graphName = "http://www.gun3y.com/ontology/movie";

    private TripleStore store = new TripleStore(graphName);

    private GenericRuleReasoner reasoner;

    @PostConstruct
    private void init() {
	List<Rule> rules = Rule.parseRules(resourceLoader.getReasoningRules());
	reasoner = new GenericRuleReasoner(rules);
	reasoner.setOWLTranslation(true);
	reasoner.setTransitiveClosureCaching(true);
    }

    public void clear() {
	this.store.clear();
    }

    public synchronized List<Movie> getMovies() {
	List<Movie> movies = new ArrayList<Movie>();

	List<QuerySolution> retList = this.store.executeSelectQuery(SparqlHelper.selectMovieNames());

	if (retList == null || retList.isEmpty()) {
	    return movies;
	}
	for (QuerySolution querySolution : retList) {
	    RDFNode node = querySolution.get("name");
	    if (node.isLiteral()) {
		Movie movie = getMovieByName(node.asLiteral().getString());
		if (movie != null) {
		    movies.add(movie);
		}
	    }
	}
	return movies;
    }

    public synchronized Movie getMovieByName(String movieName) {
	if (StringUtils.isBlank(movieName)) {
	    throw new SWException(ExceptionCode.InvalidInput);
	}
	String tempMovieName = movieName.toLowerCase(Locale.ENGLISH);

	List<QuerySolution> retList = this.store.executeSelectQuery(SparqlHelper.selectMovieByName(tempMovieName));

	if (retList == null || retList.isEmpty()) {
	    return null;
	}
	Movie movie = new Movie();
	movie.setName(tempMovieName);
	Map<PropertyType, List<Data>> propMap = new HashMap<PropertyType, List<Data>>();

	for (QuerySolution querySolution : retList) {
	    RDFNode pNode = querySolution.get("predicate");
	    if (pNode != null && pNode.isResource()) {
		PropertyType propertyType = PropertyType.getPropertyType((pNode.asResource()).getURI());
		RDFNode pNodeText = querySolution.get("dataNodeText");
		RDFNode pNodeFrom = querySolution.get("dataNodeFrom");

		if (!propMap.containsKey(propertyType)) {
		    propMap.put(propertyType, new ArrayList<Data>());
		}
		propMap.get(propertyType).add(new Data(pNodeText.asLiteral().getString(), pNodeFrom.asLiteral().getString()));
	    }
	}
	for (Entry<PropertyType, List<Data>> entry : propMap.entrySet()) {

	    List<Data> dataList = entry.getValue();
	    if (dataList != null) {
		Collections.sort(dataList, new Comparator<Data>() {
		    @Override
		    public int compare(Data arg0, Data arg1) {
			return arg0.getText().compareTo(arg1.getText());
		    }
		});
	    }

	    entry.getKey().setData(movie, entry.getValue());
	}

	return movie;
    }

    public synchronized void updateMovie(String from, List<ResultField> resultFields) {
	System.out.println("Starts:" + from);
	if (StringUtils.isBlank(from)) {
	    throw new SWException(ExceptionCode.InvalidInput);
	}

	if (resultFields == null || resultFields.isEmpty()) {
	    return;
	}
	List<ResultField> nameList = Commons.sublist(resultFields, PropertyType.name);
	if (nameList == null || nameList.isEmpty() || nameList.get(0) == null || StringUtils.isBlank(nameList.get(0).getValue())) {
	    return;
	}
	String movieName = nameList.get(0).getValue();
	movieName = movieName.toLowerCase(Locale.ENGLISH);
	AnonId movieAnonId = findMovieByName(movieName);
	if (movieAnonId == null) {
	    movieAnonId = createMovie(movieName);
	}

	updateMovie(movieAnonId, movieName, PropertyType.actor, from, Commons.sublist(resultFields, PropertyType.actor));
	updateMovie(movieAnonId, movieName, PropertyType.contentRating, from, Commons.sublist(resultFields, PropertyType.contentRating));
	updateMovie(movieAnonId, movieName, PropertyType.creator, from, Commons.sublist(resultFields, PropertyType.creator));
	updateMovie(movieAnonId, movieName, PropertyType.datePublished, from, Commons.sublist(resultFields, PropertyType.datePublished));
	updateMovie(movieAnonId, movieName, PropertyType.description, from, Commons.sublist(resultFields, PropertyType.description));
	updateMovie(movieAnonId, movieName, PropertyType.director, from, Commons.sublist(resultFields, PropertyType.director));
	updateMovie(movieAnonId, movieName, PropertyType.duration, from, Commons.sublist(resultFields, PropertyType.duration));
	updateMovie(movieAnonId, movieName, PropertyType.genre, from, Commons.sublist(resultFields, PropertyType.genre));
	updateMovie(movieAnonId, movieName, PropertyType.image, from, Commons.sublist(resultFields, PropertyType.image));
	updateMovie(movieAnonId, movieName, PropertyType.keywords, from, Commons.sublist(resultFields, PropertyType.keywords));
	updateMovie(movieAnonId, movieName, PropertyType.productionCompany, from, Commons.sublist(resultFields, PropertyType.productionCompany));
	updateMovie(movieAnonId, movieName, PropertyType.provider, from, Commons.sublist(resultFields, PropertyType.provider));
	updateMovie(movieAnonId, movieName, PropertyType.rating, from, Commons.sublist(resultFields, PropertyType.rating));
	updateMovie(movieAnonId, movieName, PropertyType.url, from, Commons.sublist(resultFields, PropertyType.url));
	this.store.executeReasoner(this.reasoner);
	System.out.println("Ends:" + from);
    }

    private void updateMovie(AnonId movieAnonId, String movieName, PropertyType propertyType, String from, List<ResultField> fields) {
	if (movieAnonId == null || StringUtils.isBlank(movieName) || propertyType == null || StringUtils.isBlank(from)) {
	    throw new SWException(ExceptionCode.InvalidInput);
	}

	if (fields == null || fields.isEmpty()) {
	    return;
	}
	for (ResultField resultField : fields) {
	    if (resultField != null && StringUtils.isNotBlank(resultField.getValue())) {
		updateMovie(movieAnonId, movieName, propertyType, resultField.getValue(), from);
	    }
	}

    }

    private void updateMovie(AnonId movieAnonId, String movieName, PropertyType propertyType, String text, String from) {

	AnonId dataAnonId = askByValue(movieName, propertyType, text);
	AnonId dataAnonIdWithFrom = askByDomain(movieName, propertyType, text, from);
	List<Triple> triples = new ArrayList<Triple>();

	if (dataAnonId == null) {
	    Node movieNode = NodeFactory.createAnon(movieAnonId);
	    Node dataNode = NodeFactory.createAnon();
	    triples.add(new Triple(movieNode, TripleHelper.createPredicate(propertyType), dataNode));
	    triples.add(new Triple(dataNode, TripleHelper.createTextPredicate(), NodeFactory.createLiteral(text)));
	    triples.add(new Triple(dataNode, TripleHelper.createFromPredicate(), NodeFactory.createLiteral(from)));

	}
	else if (dataAnonIdWithFrom == null) {
	    Node dataNode = NodeFactory.createAnon(dataAnonId);
	    triples.add(new Triple(dataNode, TripleHelper.createFromPredicate(), NodeFactory.createLiteral(from)));
	}

	this.store.addTriplesAsBulk(triples);
    }

    private AnonId createMovie(String movieName) {
	if (StringUtils.isBlank(movieName)) {
	    throw new SWException(ExceptionCode.InvalidInput);
	}
	String tempMovieName = movieName.toLowerCase(Locale.ENGLISH);
	List<Triple> triples = new ArrayList<Triple>();
	Node movieNode = NodeFactory.createAnon();
	triples.add(new Triple(movieNode, TripleHelper.createTypePredicate(), TripleHelper.createMovie()));
	triples.add(new Triple(movieNode, TripleHelper.createNamePredicate(), NodeFactory.createLiteral(tempMovieName)));
	this.store.addTriplesAsBulk(triples);
	return movieNode.getBlankNodeId();
    }

    private AnonId findMovieByName(String movieName) {
	if (StringUtils.isBlank(movieName)) {
	    throw new SWException(ExceptionCode.InvalidInput);
	}
	String tempMovieName = movieName.toLowerCase(Locale.ENGLISH);

	return this.store.executeAskAnonId(SparqlHelper.askByName(tempMovieName));
    }

    private AnonId askByValue(String movieName, PropertyType propertyType, String text) {
	if (StringUtils.isBlank(movieName) || propertyType == null || StringUtils.isBlank(text)) {
	    throw new SWException(ExceptionCode.InvalidInput);
	}
	String tempMovieName = movieName.toLowerCase(Locale.ENGLISH);

	return this.store.executeAskAnonId(SparqlHelper.askByValue(tempMovieName, propertyType, text));
    }

    private AnonId askByDomain(String movieName, PropertyType propertyType, String text, String from) {
	if (StringUtils.isBlank(movieName) || propertyType == null || StringUtils.isBlank(text) || StringUtils.isBlank(from)) {
	    throw new SWException(ExceptionCode.InvalidInput);
	}
	String tempMovieName = movieName.toLowerCase(Locale.ENGLISH);

	return this.store.executeAskAnonId(SparqlHelper.askByDomain(tempMovieName, propertyType, text, from));
    }
}
